package com.hippoframework.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

/**
 * class工具类
 *
 * @author YichuanWang
 * @date 2019/12/10 17:39
 */
@Slf4j
public class ClassUtils extends org.apache.commons.lang3.ClassUtils {

    /**
     * 获取类加载器
     *
     * @return {@link ClassLoader} 当前线程ClassLoader
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 加载Class
     *
     * @param className     class名称
     * @param isInitialized 是否检查已加载
     * @return 加载的Class对象
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            log.error("load class failure", e);
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * 获取指定包名下的所有类
     *
     * @param packageName 包名
     * @return Set<Class < ?>> 类集合
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        return ClassUtil.scanPackage(packageName);
    }

    public static String getQualifiedName(Class<?> clazz) {
        Assert.notNull(clazz, "Class must not be null");
        return clazz.getTypeName();
    }

    /**
     * 获得目标Type的泛型
     *
     * @param type 被查询Type
     * @return 泛型Types
     */
    public static Type[] getGenerics(Type type) {
        if (type instanceof Class) {
            return ((Class<?>) type).getTypeParameters();
        } else if (type instanceof ParameterizedType) {
            return ((ParameterizedType) type).getActualTypeArguments();
        }
        return new Type[0];
    }

    public static Type[] getGenerics(Field field) {
        return getGenerics(field.getGenericType());
    }

    /**
     * cls是否能够注入field
     * <p>例如：UserServiceImpl能够注入UserService
     *
     * @param cls          被注入class，通常为接口实现类
     * @param field        待注入field，通常为接口
     * @param checkGeneric 是否检查泛型
     */
    public static boolean isInjectable(Class<?> cls, Field field, boolean checkGeneric) {
        Class<?> toCls = field.getType();
        boolean assignable = isAssignable(cls, toCls);
        if (!checkGeneric || !assignable) {
            return assignable;
        }
        Type toGenericType = field.getGenericType();
        //若field没有泛型，则忽略泛型检查
        if (toGenericType instanceof Class) {
            return assignable;
        }
        //目标Class拥有的泛型
        Type[] toGenerics = getGenerics(toGenericType);
        //当前Class及家族中与目标Class相匹配的Type
        Type type = matchType(cls, toCls);

        //当前Class家族匹配的泛型
        Type[] currentGenerics = getGenerics(type);
        //若泛型数量不同，则直接不匹配
        if (toGenerics.length != currentGenerics.length) {
            return false;
        }
        for (Type currentGeneric : currentGenerics) {
            if (!ArrayUtil.contains(toGenerics, currentGeneric)) {
                return false;
            }
        }
        return true;
    }

    private static Type matchType(Class<?> cls, Class<?> toCls) {
        if (cls == toCls) {
            return cls;
        }
        return matchInheritType(cls, toCls);
    }

    private static Type matchType(ParameterizedType cls, Class<?> toCls) {
        Class<?> rawClass = (Class<?>) cls.getRawType();
        if (rawClass == toCls) {
            return cls;
        }
        return matchInheritType(rawClass, toCls);
    }

    private static Type matchInheritType(Class<?> cls, Class<?> toCls) {
        Type[] genericInterfaces = cls.getGenericInterfaces();
        for (Type genericInterface : genericInterfaces) {
            if (genericInterface instanceof Class) {
                return matchType((Class<?>) genericInterface, toCls);
            } else if (genericInterface instanceof ParameterizedType) {
                return matchType((ParameterizedType) genericInterface, toCls);
            }
        }
        return null;
    }

    /**
     * Return the qualified name of the given method, consisting of
     * fully qualified interface/class name + "." + method name.
     *
     * @param method the method
     * @return the qualified name of the method
     */
    public static String getQualifiedMethodName(Method method) {
        return getQualifiedMethodName(method, null);
    }

    /**
     * Return the qualified name of the given method, consisting of
     * fully qualified interface/class name + "." + method name.
     *
     * @param method the method
     * @param clazz  the clazz that the method is being invoked on
     *               (may be {@code null} to indicate the method's declaring class)
     * @return the qualified name of the method
     */
    public static String getQualifiedMethodName(Method method, @NonNull Class<?> clazz) {
        Assert.notNull(method, "Method must not be null");
        return (clazz != null ? clazz : method.getDeclaringClass()).getName() + '.' + method.getName();
    }


}
